﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace LightCAD.MathLib.CSG
{
    public partial class Vec2
    {
        #region Properties

        public double x;
        public double y;

        #endregion
        public Vec2() : this(0, 0)
        { }
        public Vec2(double x = 0, double y = 0)
        {
            this.x = x;
            this.y = y;
        }
        public double this[int i]
        {
            get
            {
                return i == 0 ? this.x : i == 1 ? this.y : throw new Exception("error index");
            }
            set
            {
                if (i == 0) this.x = value;
                else if (i == 1) this.y = value;
                else throw new Exception("error index");
            }
        }
        public double Width
        {
            [MethodImpl((MethodImplOptions)768)]
            get
            {
                return x;
            }
            [MethodImpl((MethodImplOptions)768)]
            set
            {
                x = value;
            }
        }
        public double Height
        {
            [MethodImpl((MethodImplOptions)768)]
            get
            {
                return y;
            }
            [MethodImpl((MethodImplOptions)768)]
            set
            {
                y = value;
            }
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Set(double x, double y)
        {
            this.x = x;
            this.y = y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 SetScalar(double scalar)
        {
            x = scalar;
            y = scalar;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 SetX(double x)
        {
            this.x = x;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 SetY(double y)
        {
            this.y = y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 SetComponent(double index, double value)
        {
            switch (index)
            {
                case 0: x = value; break;
                case 1: y = value; break;
                default: throw new Error("index is out of range: " + index);
            }
            return this;
        }
        public double GetComponent(double index)
        {
            switch (index)
            {
                case 0: return x;
                case 1: return y;
                default: throw new Error("index is out of range: " + index);
            }
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 clone()
        {
            return new Vec2(x, y);
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Copy(Vec2 v)
        {
            x = v.x;
            y = v.y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Add(Vec2 v)
        {
            x += v.x;
            y += v.y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Abs(Vec2 v)
        {
            x = Math.Abs(v.x);
            y = Math.Abs(v.y);
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 AddScalar(double s)
        {
            x += s;
            y += s;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 AddVectors(Vec2 a, Vec2 b)
        {
            x = a.x + b.x;
            y = a.y + b.y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 AddScaledVector(Vec2 v, double s)
        {
            x += v.x * s;
            y += v.y * s;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Sub(Vec2 v)
        {
            x -= v.x;
            y -= v.y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 SubScalar(double s)
        {
            x -= s;
            y -= s;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 SubVectors(Vec2 a, Vec2 b)
        {
            x = a.x - b.x;
            y = a.y - b.y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Multiply(Vec2 v)
        {
            x *= v.x;
            y *= v.y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 MultiplyScalar(double scalar)
        {
            x *= scalar;
            y *= scalar;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Divide(Vec2 v)
        {
            x /= v.x;
            y /= v.y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 DivideScalar(double scalar)
        {
            return MultiplyScalar(1 / scalar);
        }

        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Min(Vec2 v)
        {
            x = Math.Min(x, v.x);
            y = Math.Min(y, v.y);
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Max(Vec2 v)
        {
            x = Math.Max(x, v.x);
            y = Math.Max(y, v.y);
            return this;
        }


        [MethodImpl((MethodImplOptions)768)]
        public double Min()
        {
            return Math.Min(this.x, this.y);
        }
        [MethodImpl((MethodImplOptions)768)]
        public double Max()
        {
            return Math.Max(this.x, this.y);
        }
        public Vec2 abs()
        {
            this.x = Math.Abs(this.x);
            this.y= Math.Abs(this.y);
            return this;
        }
        public int maxDim()
        {
            return (this.x >= this.y) ? 0 : 1;
        }
        public int minDim()
        {
            return (this.x <= this.y) ? 0 : 1;
        }


        public Vec2 Clamp(Vec2 min, Vec2 max)
        {
            // assumes min < max, componentwise
            x = Math.Max(min.x, Math.Min(max.x, x));
            y = Math.Max(min.y, Math.Min(max.y, y));
            return this;
        }
        public Vec2 ClampScalar(double minVal, double maxVal)
        {
            x = Math.Max(minVal, Math.Min(maxVal, x));
            y = Math.Max(minVal, Math.Min(maxVal, y));
            return this;
        }
        public Vec2 ClampLength(double min, double max)
        {
            var length = Length();
            return DivideScalar(length == 0 ? 1 : length).MultiplyScalar(Math.Max(min, Math.Min(max, length)));
        }
        public Vec2 Floor()
        {
            x = Math.Floor(x);
            y = Math.Floor(y);
            return this;
        }
        public Vec2 Ceil()
        {
            x = Math.Ceiling(x);
            y = Math.Ceiling(y);
            return this;
        }
        public Vec2 Round()
        {
            x = Math.Round(x);
            y = Math.Round(y);
            return this;
        }
        public Vec2 RoundToZero()
        {
            x = x < 0 ? Math.Ceiling(x) : Math.Floor(x);
            y = y < 0 ? Math.Ceiling(y) : Math.Floor(y);
            return this;
        }
        public Vec2 Negate()
        {
            x = -x;
            y = -y;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public double Dot(Vec2 v)
        {
            return x * v.x + y * v.y;
        }
        [MethodImpl((MethodImplOptions)768)]
        public double Cross(Vec2 v)//// determinant of 2 vec2s
        {
            return x * v.y - y * v.x;
        }
        [MethodImpl((MethodImplOptions)768)]
        public double LengthSq()
        {
            return x * x + y * y;
        }
        [MethodImpl((MethodImplOptions)768)]
        public double Length()
        {
            return Math.Sqrt(x * x + y * y);
        }
        [MethodImpl((MethodImplOptions)768)]
        public double ManhattanLength()
        {
            return Math.Abs(x) + Math.Abs(y);
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Normalize()
        {
            var len = Length();
            if (len == 0) return this;
            x /= len;
            y /= len;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 GetNormalized()
        {
            var len = Length();
            if (len == 0) return this.clone();
            return new Vec2(this.x / len, this.y / len);
        }

        [MethodImpl((MethodImplOptions)768)]
        public double Angle()
        {
            // computes the angle in radians with respect to the positive x-axis
            double angle = Math.Atan2(-y, -x) + MathEx.PI;
            return angle;
        }
        [MethodImpl((MethodImplOptions)768)]
        public double AngleTo(Vec2 v)
        {
            var denominator = Math.Sqrt(LengthSq() * v.LengthSq());
            if (denominator == 0) return MathEx.PI / 2;
            var theta = Dot(v) / denominator;

            // clamp, to handle numerical problems

            return Math.Acos(MathEx.Clamp(theta, -1, 1));

        }
        [MethodImpl((MethodImplOptions)768)]
        public double DistanceTo(Vec2 v)
        {
            return Math.Sqrt(DistanceToSquared(v));
        }
        [MethodImpl((MethodImplOptions)768)]
        public double DistanceToSquared(Vec2 v)
        {
            double dx = x - v.x, dy = y - v.y;
            return dx * dx + dy * dy;
        }
        [MethodImpl((MethodImplOptions)768)]
        public double ManhattanDistanceTo(Vec2 v)
        {
            return Math.Abs(x - v.x) + Math.Abs(y - v.y);
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 SetLength(double length)
        {
            return Normalize().MultiplyScalar(length);
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 Lerp(Vec2 v, double alpha)
        {
            x += (v.x - x) * alpha;
            y += (v.y - y) * alpha;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 LerpVectors(Vec2 v1, Vec2 v2, double alpha)
        {
            x = v1.x + (v2.x - v1.x) * alpha;
            y = v1.y + (v2.y - v1.y) * alpha;
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public bool Equals(Vec2 v)
        {
            return (MathEx.IsEqual(x, v.x) && MathEx.IsEqual(y, v.y));
        }
        [MethodImpl((MethodImplOptions)768)]
        public override bool Equals(object obj)
        {
            return Equals(obj as Vec2);
        }

        [MethodImpl((MethodImplOptions)768)]
        public Vec2 FromArray(double[] array, int offset = 0)
        {
            x = array[offset];
            y = array[offset + 1];
            return this;
        }
        [MethodImpl((MethodImplOptions)768)]
        public double[] ToArray(double[] array = null, int offset = 0)
        {
            if (array == null) array = new double[2];
            array[offset] = x;
            array[offset + 1] = y;
            return array;
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 RotateAround(Vec2 center, double angle)
        {
            return RotateAround(center.x, center.y, angle);
        }
        [MethodImpl((MethodImplOptions)768)]
        public Vec2 RotateAround(double cx, double cy, double angle)
        {
            double c = Math.Cos(angle), s = Math.Sin(angle);
            double x = this.x - cx;
            double y = this.y - cy;
            this.x = x * c - y * s + cx;
            this.y = x * s + y * c + cy;
            return this;
        }
        public Vec2 Random()
        {
            x = MathEx.Random();
            y = MathEx.Random();
            return this;
        }

        [MethodImpl((MethodImplOptions)768)]
        public Vec3 ToVector3()
        {
            return new Vec3(x, y, 0);
        }

        public override string ToString()
        {
            return $"x: {x}, y: {y}";
        }


        [MethodImpl((MethodImplOptions)768)]
        public void Set(double? newX, double? newY, double? newData)
        {
            if (newX != null) this.x = newX.Value;
            if (newY != null) this.y = newY.Value;
            //if (newData != null) this.Data = newData.Value;
        }

        public static Vec2 UnitX => new Vec2(1, 0);
        public static Vec2 UnitY => new Vec2(0, 1);
        public static Vec2 Zero => new Vec2(0, 0);
        public static Vec2 One => new Vec2(1, 1);

        //public override void Copy(Vec2 from)
        //{
        //    this.X = from.X;
        //    this.Y = from.Y;
        //    //this.Data= from.Data;
        //}

        //public Vec2 Clone()
        //{
        //    return new Vec2(this.X, this.Y);
        //}
        public bool Similarity(Vec2 a, double delta = 1)
        {
            if (a == this)
                return true;
            else if (Vec2.Distance(this, a) < delta)
                return true;
            else
                return false;

        }

        [MethodImpl((MethodImplOptions)768)]
        public static double Dot(Vec2 a, Vec2 b)
        {
            return a.x * b.x + a.y * b.y;
        }
        [MethodImpl((MethodImplOptions)768)]
        public static double Cross(Vec2 a, Vec2 b)
        {
            return ((a.x * b.y) - (a.y * b.x));
        }

        ///// <summary>
        ///// Returns the unsigned angle in degrees between a and b.
        ///// The smaller of the two possible angles between the two vectors is used.
        ///// The result value range: [0, 180]
        ///// </summary>
        //public static double Angle(Vec2 a, Vec2 b)
        //{
        //    return MathEx.RadianToDegree(AngleInRadian(a, b));
        //}


        /// <summary>
        /// Obtains the angle of a line defined by two points.
        /// </summary>
        /// <param name="u">A Vec2.</param>
        /// <param name="v">A Vec2.</param>
        /// <returns>Angle in radians.</returns>
        [MethodImpl((MethodImplOptions)768)]
        public static double GetAngle(Vec2 u, Vec2 v)
        {
            Vec2 dir = v - u;
            return dir.Angle();
        }


        /// <summary>
        /// Returns the unsigned angle in radians between a and b.
        /// The smaller of the two possible angles between the two vectors is used.
        /// The result value range: [0, PI]
        /// </summary>
        public static double AngleInRadian(Vec2 a, Vec2 b)
        {
            double num = a.Length() * b.Length();
            if (num == 0.0)
            {
                return 0.0;
            }
            double num2 = Dot(a, b) / num;
            return Math.Acos(MathEx.Clamp(num2, -1.0, 1.0));
        }

        /// <summary>
        /// Returns the signed acute clockwise angle in degrees between from and to.
        /// The result value range: [-180, 180]
        /// </summary>
        [MethodImpl((MethodImplOptions)768)]
        public static double SignedAngle(Vec2 from, Vec2 to)
        {
            return MathEx.RadianToDegree(SignedAngleInRadian(from, to));
        }

        /// <summary>
        /// Returns the signed acute clockwise angle in radians between from and to.
        /// The result value range: [-PI, PI]
        /// </summary>
        public static double SignedAngleInRadian(Vec2 from, Vec2 to)
        {
            double rad = AngleInRadian(from, to);
            if (Cross(from, to) < 0)
            {
                rad = -rad;
            }
            return rad;
        }
        [MethodImpl((MethodImplOptions)768)]
        public static double Distance(Vec2 a, Vec2 b)
        {
            Vec2 vector = b - a;
            return vector.Length();
        }

        public static double AnglesDifference(double firstAngle, double secondAngle)
        {
            double difference = secondAngle - firstAngle;
            while (difference < -MathEx.PI) difference += MathEx.TwoPI;
            while (difference > MathEx.PI) difference -= MathEx.TwoPI;

            return difference;
        }

        // Evaluates if the points are clockwise.
        [MethodImpl((MethodImplOptions)768)]
        public static bool Clockwise(Vec2 p1, Vec2 p2, Vec2 p3)
        {
            return ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)) < 1e-8;
        }
        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 Polar(Vec2 u, double distance, double angle)
        {
            Vec2 dir = new Vec2(Math.Cos(angle), Math.Sin(angle));
            return u + dir * distance;
        }
        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 Rotate(Vec2 v, double angle)
        {
            return RotateInRadian(v, MathEx.DegreeToRadian(angle));
        }
        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 Rotate(Vec2 point, Vec2 basePoint, double angle)
        {
            return RotateInRadian(point, basePoint, MathEx.DegreeToRadian(angle));
        }
        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 RotateInRadian(Vec2 v, double rad)
        {
            double x = v.x * Math.Cos(rad) - v.y * Math.Sin(rad);
            double y = v.x * Math.Sin(rad) + v.y * Math.Cos(rad);
            return new Vec2(x, y);
        }

        /// <summary>
        /// Rotates one point around another TM
        /// </summary>
        /// <param name="pointToRotate">The point to rotate.</param>
        /// <param name="centerPoint">The center point of rotation.</param>
        /// <param name="angleInDegrees">The rotation angle in degrees.</param>
        /// <returns>Rotated point</returns>
        public static Vec2 RotateInRadian(Vec2 pointToRotate, Vec2 centerPoint, double angleInRadians)
        {
            double cosTheta = Math.Cos(angleInRadians);
            double sinTheta = Math.Sin(angleInRadians);
            return new Vec2
            {
                x = (cosTheta * (pointToRotate.x - centerPoint.x) - sinTheta * (pointToRotate.y - centerPoint.y) + centerPoint.x),
                y = (sinTheta * (pointToRotate.x - centerPoint.x) + cosTheta * (pointToRotate.y - centerPoint.y) + centerPoint.y)
            };
        }

        public static Vec2 PointOrthoMode(Vec2 last, Vec2 point, bool ortho)
        {
            if (ortho)
            {
                if (Math.Abs(point.x - last.x) > Math.Abs(point.y - last.y))
                    return new Vec2(point.x, last.y);
                else
                    return new Vec2(last.x, point.y);
            }
            else
            {
                return point;
            }


        }
        public Vec2 Mirror(Vec2 axisStart, Vec2 axisDir)
        {
            var tmpVec = (this - axisStart);
            var prjLen = tmpVec.Dot(axisDir);
            var prjPoint = tmpVec.Copy(axisStart).AddScaledVector(axisDir, prjLen);
            tmpVec.SubVectors(prjPoint, this);
            this.Add(tmpVec.MultiplyScalar(2));
            return this;
        }


        // dimension logic for projections
        public int MaxDim()
        {
            return (this.x >= this.y) ? 0 : 1;
        }
        public int MinDim()
        {
            return (this.x <= this.y) ? 0 : 1;
        }
        public double ProjectDim(int dim)
        {
            return (dim == 0) ? this.y : this.x;
        }


        [MethodImpl((MethodImplOptions)768)]
        public static bool operator ==(Vec2 lhs, Vec2 rhs)
        {
            return lhs.x == rhs.x && lhs.y == rhs.y;
        }
        [MethodImpl((MethodImplOptions)768)]
        public static bool operator !=(Vec2 lhs, Vec2 rhs)
        {
            return lhs.x != rhs.x || lhs.y != rhs.y;
        }

        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 operator +(Vec2 a, Vec2 b)
        {
            return new Vec2(a.x + b.x, a.y + b.y);
        }

        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 operator -(Vec2 a, Vec2 b)
        {
            return new Vec2(a.x - b.x, a.y - b.y);
        }

        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 operator -(Vec2 a)
        {
            return new Vec2(-a.x, -a.y);
        }

        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 operator *(Vec2 a, double d)
        {
            return new Vec2(a.x * d, a.y * d);
        }
        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 operator *(double d, Vec2 a)
        {
            return new Vec2(a.x * d, a.y * d);
        }
        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 operator /(Vec2 a, double d)
        {
            return new Vec2(a.x / d, a.y / d);
        }

        [MethodImpl((MethodImplOptions)768)]
        public static Vec2 operator *(Vec2 left, Vec2 right)
        {
            return new Vec2(
                left.x * right.x,
                left.y * right.y
            );
        }
        public static Vec2 max(Vec2 lhs, Vec2 rhs)
        {
            return new Vec2(Math.Max(lhs.x, rhs.x),
                            Math.Max(lhs.y, rhs.y));
        }
        public static Vec2 min(Vec2 lhs, Vec2 rhs)
        {
            return new Vec2(Math.Min(lhs.x, rhs.x),
                            Math.Min(lhs.y, rhs.y));
        }
        public static double det(Vec2 lhs, Vec2 rhs)
        {
            return lhs.x * rhs.y - lhs.y * rhs.x;
        }
    }
}
